import json
from matplotlib import pyplot as plt
import math
import numpy as np
from scipy.spatial.distance import pdist


class DataAnalyzer:
    @staticmethod
    def __get_aspect_ratios(classnames, boundingboxes):
        """
        将需要的信息集合， 方便实用
        :param classnames: 数据集中所有的类别名
        :param boundingboxes: 数据集中所有的boundingbox
        :return: 返回形如[[a, b],]形式的列表
        """
        aspect_ratios = []
        for classname, boundingbox in zip(classnames, boundingboxes):
            aspect_ratios.append([classname, boundingbox.get_aspect_ratio()])
        return aspect_ratios

    @staticmethod
    def __filtrate_data(data, classnames):
        """
        过滤掉多余的信息
        :param data: 需要过滤的信息
        :param classnames: 根据类别名来过滤
        :return: 新的data
        """
        new_data = []
        for i in data:
            if classnames in i[0]:
                new_data.append(i)
        return new_data

    @staticmethod
    def __get_scale(classnames, scales):
        """
        将需要的信息集合， 方便实用
        :param classnames: 数据集中所有的类别名
        :param scales: 数据集中所有的scales
        :return: 返回形如[[a, b],]形式的列表
        """
        scales_ = []
        for classname, scale in zip(classnames, scales):
            scales_.append([classname, scale])
        return scales_

    @staticmethod
    def __deal_data(data, max_value, min_value, bins):
        """
        分析数据，得到每个bins里的分布
        :param data:需要分析的数据
        :param max_value:需要分析的最大值
        :param min_value:需要分析的最小值
        :param bins:需要分布的个数
        :return:返回数据分布信息
        """
        for i in range(len(data)):
            if data[i] >= max_value:
                data[i] = max_value
            if data[i] <= min_value:
                data[i] = min_value
        length = (max_value - min_value) / bins
        data_distribution = {}
        for i in range(bins + 1):
            data_distribution_s = []
            for j in range(len(data)):
                if min_value + length * i < data[j] < min_value + length * (i + 1):
                    data_distribution_s.append(data[j])
            data_distribution[min_value + length * i] = len(data_distribution_s) / len(data)
        return data_distribution

    @staticmethod
    def analyze_aspect_ratios(dataset, classname=None, bins=10, max_value=None, min_value=None, is_save=None):
        """
        对数据集中的目标的宽长比进行分析
        :param dataset: 需要分析的数据集
        :param classname: 如果对特定的目标进行分析，传入特定目标的类别（以list的形式传入）
        :param bins: 宽长比最终需要分割的bin
        :param max_value: 如果宽长比分布不均匀两端数据极小的情况下，可自己指定最大的比例
        :param min_value: 如果宽长比分布不均匀两端数据极小的情况下，可自己指定最小的比例
        :param is_save: 如果要保存，填写保存地址（以json格式保存）
        :return: 返回一个[[长宽比类别， 所占比例],]的列表
        """
        classnames = dataset.get_classnames()
        boundingboxes = dataset.get_boundingboxes()
        aspect_ratios = DataAnalyzer.__get_aspect_ratios(classnames, boundingboxes)
        if classname is None:
            aspect_ratio_value = []
            for aspect_ratio in aspect_ratios:
                aspect_ratio_value.append(aspect_ratio[1])
            if max_value is None and min_value is None:
                max_value = max(aspect_ratio_value)
                min_value = min(aspect_ratio_value)
                aspect_ratios_ = DataAnalyzer.__deal_data(aspect_ratio_value, max_value, min_value, bins)
            elif max_value is None and min_value is not None:
                max_value = max(aspect_ratio_value)
                aspect_ratios_ = DataAnalyzer.__deal_data(aspect_ratio_value, max_value, min_value, bins)
            elif min_value is None and max_value is not None:
                min_value = min(aspect_ratio_value)
                aspect_ratios_ = DataAnalyzer.__deal_data(aspect_ratio_value, max_value, min_value, bins)
            else:
                aspect_ratios_ = DataAnalyzer.__deal_data(aspect_ratio_value, max_value, min_value, bins)
        else:
            aspect_ratios = DataAnalyzer.__filtrate_data(aspect_ratios, classname)
            aspect_ratio_value = []
            for aspect_ratio in aspect_ratios:
                aspect_ratio_value.append(aspect_ratio[1])
            if max_value and min_value is None:
                max_value = max(aspect_ratio_value)
                min_value = min(aspect_ratio_value)
                aspect_ratios_ = DataAnalyzer.__deal_data(aspect_ratio_value, max_value, min_value, bins)
            elif max_value is None:
                max_value = max(aspect_ratio_value)
                aspect_ratios_ = DataAnalyzer.__deal_data(aspect_ratio_value, max_value, min_value, bins)
            elif min_value is None:
                min_value = min(aspect_ratio_value)
                aspect_ratios_ = DataAnalyzer.__deal_data(aspect_ratio_value, max_value, min_value, bins)
            else:
                aspect_ratios_ = DataAnalyzer.__deal_data(aspect_ratio_value, max_value, min_value, bins)
        if is_save is not None:
            DataAnalyzer.save_to_json(is_save, aspect_ratios_, name='aspect_ratio')
            DataAnalyzer.save_hist_to_img(is_save, aspect_ratios_, bins, 'aspect_ratio_bin',
                                          'aspect_ratio', 'aspect_ratio', name='aspect_ratio')
        return aspect_ratios_

    @staticmethod
    def analyze_scales(dataset, classname=None, bins=10, max_value=None, min_value=None, is_save=None):
        """
        分析数据集的scale
        :param dataset: 需要分析的数据集
        :param classname: 如果需要分析指定的类别，传入类别
        :param bins: 分析scales分布的bins
        :param max_value: 指定最大值
        :param min_value: 指定最小值
        :param is_save: 如果要保存，填写保存地址（以json格式保存）
        :return: 返回一个[[长宽比类别， 所占比例],]的列表
        """
        classnames = dataset.get_classnames()
        data_items = dataset.get_data_items()
        scales = []
        for i in data_items:
            scales.extend(i.get_scales())
        scales = DataAnalyzer.__get_scale(classnames, scales)
        if classname is None:
            scale_value = []
            for i in scales:
                scale_value.append(i[1])
            if max_value is None and min_value is None:
                max_value = max(scale_value)
                min_value = min(scale_value)
                scales_ = DataAnalyzer.__deal_data(scale_value, max_value, min_value, bins)
            elif max_value is None and min_value is not None:
                max_value = max(scale_value)
                scales_ = DataAnalyzer.__deal_data(scale_value, max_value, min_value, bins)
            elif min_value is None and max_value is not None:
                min_value = min(scale_value)
                scales_ = DataAnalyzer.__deal_data(scale_value, max_value, min_value, bins)
            else:
                scales_ = DataAnalyzer.__deal_data(scale_value, max_value, min_value, bins)
        else:
            scales = DataAnalyzer.__filtrate_data(scales, classname)
            scale_value = []
            for scale in scales:
                scale_value.append(scale[1])
            if max_value is None and min_value is None:
                max_value = max(scale_value)
                min_value = min(scale_value)
                scales_ = DataAnalyzer.__deal_data(scale_value, max_value, min_value, bins)
            elif max_value is None and min_value is not None:
                max_value = max(scale_value)
                scales_ = DataAnalyzer.__deal_data(scale_value, max_value, min_value, bins)
            elif min_value is None and max_value is not None:
                min_value = min(scale_value)
                scales_ = DataAnalyzer.__deal_data(scale_value, max_value, min_value, bins)
            else:
                scales_ = DataAnalyzer.__deal_data(scale_value, max_value, min_value, bins)
        if is_save is not None:
            DataAnalyzer.save_to_json(is_save, scales_, name='scales')
            DataAnalyzer.save_hist_to_img(is_save, scales_, bins, 'scales_bin',
                                          'scales_', 'scales', name='scales')
        return scales_

    @staticmethod
    def analyze_sample_distribution(dataset, classes_name=None, is_save=None, min_num=None):
        """
        分析数据集中样本的分布情况
        :param min_num:
        :param dataset: 需要分析的数据集
        :param classes_name: 如果对特定的目标进行分析，传入特定目标的类别
        :param is_save: 如果要保存，填写保存地址（以json格式保存）
        :return:返回一个[[类别， 个数],]的列表
        """
        if classes_name is None:
            class_num = []
            class_names_alone = []
            class_names = dataset.get_classnames()
            for class_name in class_names:
                if class_name not in class_names_alone:
                    class_names_alone.append(class_name)
            for class_name in class_names_alone:
                cnt = 0
                for class_name_0 in class_names:
                    if class_name == class_name_0:
                        cnt += 1
                if min_num is None:
                    class_num.append([class_name, cnt])
                else:
                    if cnt > min_num:
                        class_num.append([class_name, cnt])
            classname_num = {}
            for class_name in class_num:
                classname_num[class_name[0]] = class_name[1]
            if is_save is not None:
                DataAnalyzer.save_to_json(is_save, classname_num, 'sample_distribution')
                print(class_num)
                DataAnalyzer.save_hist_to_img(is_save, classname_num, len(class_num), 'sample',
                                              'sample_distribution', 'sample_distribution', name='sample_distribution')
            return classname_num
        else:
            class_num = []
            class_names_alone = []
            class_names = dataset.get_classnames()
            filter_class_name = []
            for label in class_names:
                if classes_name in label:
                    filter_class_name.append(label)
            for class_name in filter_class_name:
                if class_name not in class_names_alone:
                    class_names_alone.append(class_name)
            for class_name in class_names_alone:
                cnt = 0
                for class_name_0 in filter_class_name:
                    if class_name == class_name_0:
                        cnt += 1
                if min_num is None:
                    class_num.append([class_name, cnt])
                else:
                    if cnt > min_num:
                        class_num.append([class_name, cnt])
            classname_num = {}
            for class_name in class_num:
                classname_num[class_name[0]] = class_name[1]
            if is_save is not None:
                DataAnalyzer.save_to_json(is_save, classname_num, 'sample_distribution')
                DataAnalyzer.save_hist_to_img(is_save, classname_num, len(class_num), 'sample',
                                              'sample_distribution', 'sample_distribution', name='sample_distribution')
            return classname_num

    @staticmethod
    def __vectorcosine(x, y):
        """
        计算向量夹角余弦
        :param x:
        :param y:
        :return:
        """
        vc = []
        for i in range(1, len(x) - 2):
            xc1 = x[i] - x[i - 1]
            xc2 = x[i + 1] - x[i]
            yc1 = y[i] - y[i - 1]
            yc2 = y[i + 1] - y[i]
            print((xc1 * xc2 + yc1 * yc2) / (math.sqrt(xc1 ** 2 + yc1 ** 2) * math.sqrt(xc2 ** 2 + yc2 ** 2)), '\n')
            vc.append((xc1 * xc2 + yc1 * yc2) / (math.sqrt(xc1 ** 2 + yc1 ** 2) * math.sqrt(xc2 ** 2 + yc2 ** 2)))
        return vc

    @staticmethod
    def analyze_target_consistency(dataset_train, dataset_test):
        """
        比较两个数据集数据分布的一致性
        :param dataset_train: 训练集数据集
        :param dataset_test: 测试集数据集
        :return: 返回bool型的数据
        """
        train_num = DataAnalyzer.analyze_sample_distribution(dataset_train)
        test_num = DataAnalyzer.analyze_sample_distribution(dataset_test)
        train_ratio = []
        test_ratio = []
        all_train_target = len(dataset_train.get_classnames())
        all_test_target = len(dataset_test.get_classnames())
        for num in train_num.keys():
            train_ratio.append([num, float(train_num[num]) / all_train_target])
        for num in test_num.keys():
            test_ratio.append([num, float(test_num[num]) / all_test_target])
        train = []
        test = []
        test_name = dataset_test.get_classnames()
        for i in train_ratio:
            train.append(i[1])
            if i[0] in test_name:
                for j in test_ratio:
                    if i[0] == j[0]:
                        test.append(j[1])
            else:
                test.append(0.0)
        train_0 = np.array(train)
        test_0 = np.array(test)
        print(sum(train_0), '\n', sum(test_0))
        cos1 = np.vstack([train, test])
        p1 = 1 - pdist(cos1, 'cosine')
        if p1 > 0.8:
            return True
        else:
            return False

    @staticmethod
    def save_to_json(path, data, name):
        """
        保存数据为json格式
        :param path:保存路径
        :param data: 需要保存的数据
        :param name: 需要保存的名字
        """
        path = path + '/' + name + '.json'
        with open(path, 'w', encoding='utf-8') as json_file:
            json.dump(data, json_file, ensure_ascii=False)

    @staticmethod
    def save_hist_to_img(path, data, bins, x_label, y_label, title, name):
        """
        将数据画成直方图，数据可视化
        :param path: 直方图保存路径
        :param data: 需要转化的数据(形如[[a, b],]的格式)
        :param bins: 需要保存的bin的个数
        :param x_label: X轴的命名
        :param y_label: Y轴的命名
        :param title: 直方图的标题名
        :param name: 直方图的保存名
        """
        data_key = list(data.keys())
        data_value = list(data.values())
        plt.plot(data_key, data_value)
        plt.xlabel(x_label)
        plt.ylabel(y_label)
        plt.title(title)
        path = path + '/' + name + '.jpg'
        plt.savefig(path)
